/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package org.dwh.trigger;

import java.net.SocketException;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;

import java.util.LinkedList;
import static org.dwh.trigger.Out.*;

/**
 *
 * @author Jose Carlos Palma
 * @version v1.0.0
 */
public class ClientSocket extends ThreadSocket {

    /**
     * Almacena los triggers de los clientes origen.
     */
    private static LinkedList<Trigger> triggers = new LinkedList<Trigger>();
    /**
     * Es el enlace de red con el cliente, ya sea origen o destino.
     */
    private Socket client;
    /**
     * Almacena la dirección IP del cliente.
     */
    private String ip;
    /**
     * Almacena el puerto red de la conexión del cliente.
     */
    private int port;
    private Trigger lastTrigger;

    /**
     * Crea una instancia ClientSocket de tipo origen(Source).
     * @param name es el nombre del cliente.
     * @param socket es el enlace de red con el cliente.
     */
    public ClientSocket(String name, Socket socket) {
        super(name);
        setSource();
        setSocket(socket);
    }

    /**
     * Crea una instancia de ClientSocket de tipo destino(Target).
     * @param name es el nombre del cliente.
     * @param ip es la dirección IP del cliente.
     * @param port es el puerto de red de la conexión con el cliente.
     */
    public ClientSocket(String name, String ip, int port) {
        super(name);
        setTarget();
        this.ip = ip;
        this.port = port;
    }

    /**
     * Establece el enlace de red con el cliente.
     * @param client es el enlace de red con el cliente.
     */
    private void setSocket(Socket client) {
        this.client = client;
        if (this.client == null) {
            throw new IllegalArgumentException("socket client is null");
        }
        try {
            this.client.setSoTimeout(10000);
        } catch (SocketException ex) {
            error("ClientSocket " + name + " [SocketException] no pude establecer timeout, " + ex.getMessage());
        }
        this.ip = client.getInetAddress().getHostAddress();
        this.port = client.getPort();

    }

    /**
     * 
     * @return
     */
    @Override
    public String toString() {
        StringBuilder retVal = new StringBuilder("ClientSocket ");
        retVal.append(name).append(": [").append(ip).append(", ").append(port).append("]");
        return retVal.toString();
    }

    /**
     * Contiene la lógica del cliente, puede ser del tipo origen o destino.
     * @see org.dwh.trigger.ClientSocket#coreSource() 
     * @see org.dwh.trigger.ClientSocket#coreTarget() 
     */
    @Override
    public void core() {
        if (isSource()) {
            coreSource();
        } else {
            coreTarget();
        }
    }

    /**
     * Es el enlace de red generado por el ServerSocket que permite la lectura de
     * triggers y son depositado en la cola para ser procesados por el cliente
     * de tipo destino. Este método es llamado por el método ClientSocket.core().
     * @see org.dwh.trigger.ClientSocket#core()
     */
    private void coreSource() {

        info("Iniciando ClientSocket " + name + "...");

        StringBuilder sb = null;
        char c;

        while (isRunning()) {


            sb = new StringBuilder();

            try {
                //Busca el inicio de un trigger (comienza con T)
                c = (char) client.getInputStream().read();
                while (c != 'T') {
                    c = (char) client.getInputStream().read();
                }
            } catch (IOException ex) {
                error("Cerrando ClientSocket " + name + " no pudo leer la trama, " + ex.getMessage());
                shutDown();
                break;
            }
            //Agrega el inicio al buffer.
            sb.append(c);

            try {
                c = (char) client.getInputStream().read();
                while (c != ';') {
                    //Agrega la trama del trigger
                    sb.append(c);
                    c = (char) client.getInputStream().read();
                }
            } catch (IOException ex) {
                error("Cerrando ClientSocket " + name + " no pudo leer la trama, " + ex.getMessage());
                shutDown();
                break;
            }

            //Crea el trigger y lo agrega a la cola
            lastTrigger = new Trigger(name, sb);
            //info("ClientSocket " + name + " " + lastTrigger.toString());
            triggers.add(lastTrigger);

        }

        info("Cerrando ClientSocket " + name + "...");

        try {
            client.close();
        } catch (IOException ex) {
            error("Cerrando ClientSocket " + name + ex.getMessage());
        }

    }

    /**
     * Establece una conexión del tipo destino con el cliente, deposita los
     * trigger leidos por los clientes de tipo origen. Este método es llamado
     * por el método ClientSocket.core().
     * @see org.dwh.trigger.ClientSocket#core()
     */
    private void coreTarget() {

        PrintWriter pw = null;
        BufferedReader br = null;

        info("Iniciando ClientSocket " + name + "...");
        try {

            info("ClientSocket " + name + ", estableciendo enlace de red...");
            this.client = new Socket(ip, port);
            info("ClientSocket " + name + ", enlace de red [OK]");

            info("ClientSocket " + name + ", creando el enlace de salida...");
            pw = new PrintWriter(this.client.getOutputStream(), true);
            info("ClientSocket " + name + ", enlace de salida [OK]");

            info("ClientSocket " + name + ", creando el enlace de entrada...");
            br = new BufferedReader(new InputStreamReader(this.client.getInputStream()));
            info("ClientSocket " + name + ", enlace de entrada [OK]");

        } catch (UnknownHostException ex) {
            shutDown();
            error("ClientSocket " + name + " [UnknownHostException] no pudo conectarse, " + ex.getMessage());
            Runtime.getRuntime().exit(1);
        } catch (SecurityException ex) {
            shutDown();
            error("ClientSocket " + name + " [SecurityException] no pudo conectarse, " + ex.getMessage());
            Runtime.getRuntime().exit(1);
        } catch (IOException ex) {
            shutDown();
            error("ClientSocket " + name + " [IOException] no pudo conectarse, " + ex.getMessage());
            Runtime.getRuntime().exit(1);
        }
        info("ClientSocket " + name + " [OK]");

        while (isRunning()) {
            if (!triggers.isEmpty()) {

                lastTrigger = triggers.remove();
                pw.println(lastTrigger.toString());
                trigger("{" + lastTrigger.getSDP() + "} " + lastTrigger.toString());
                try {
                    //info("ClientSocket " + name + " esperando respuesta...");
                    int res = client.getInputStream().read();
                    //info("ClientSocket " + name + " respuesta [OK]");
                } catch (IOException ex) {
                    error("ClientSocket " + name + " [IOException] no pudo leer "
                            + "la respuesta del destino, " + ex.getMessage());
                }

            } else {
                pause(200);
            }
        }

        info("Cerrando ClientSocket " + name + "...");

        try {
            this.client.close();
        } catch (IOException ex) {
        }
    }
}
